package org.datamining.guha.model.node;

import java.util.LinkedList;
import java.util.List;

import org.datamining.guha.model.Formatter;
import org.datamining.guha.model.literal.Literal;


/**
 * Holds code which is common to all node implementations. 
 * 
 * @author Lukas Vlcek
 */
public abstract class AbstractNode implements Node {
	
    /** Root node has no parent (parent is null). */
	protected Node parent;
    
    /** List of all successors of this node. */
    protected LinkedList<Node> successors;
    
    /** Literal contained in this node. */
    protected Literal literal;
    
    /**
     * Frequency of Literal for this node. Must be > 0.
     * <p>
     * Note:
     * Primitive types in Java are stored in stack. We should
     * know what can happen if we have too much nodes (stack overflow?)
     */
    protected long frequency;
    
    /**
     * References to the Nodes with the same Literal.
     * If Node has no next/previousLiteralNode
     * then these properties are set to null.
     */
    protected Node nextLiteralNode;
    protected Node previousLiteralNode;

	private Formatter<Node> formatter;

    public int compareTo(Node o) {
        return literal.compareTo(o.getLiteral());
    }
    
    public Node getParent() {
        return parent;
    }
    
    public long getFrequency() {
        return frequency;
    }
    
    public void setFrequency(long frequency) {
        this.frequency = frequency;
    }
    
    /**
     * Be sure to pass positive value as an argument because there
     * is no parameter value check (performace reasons?).
     * In fact you could decrease frequency by passing negative value.
     */
    public void increaseFrequency(long increaseFactor) {
    	// TODO Consider check for negative parameter
        this.frequency += increaseFactor;
    }
    
    public int getDepth() {
        int depth = 0;
        Node n = this;
        while (!n.isRoot()) {
            depth++;
            n = n.getParent();
        }
        return depth;
    }
    
    public void increaseFrequencyUpToRoot(long increaseFactor) {
        Node n = this;
        while (!n.isRoot()) {
            n.increaseFrequency(increaseFactor);
            n = n.getParent();
        }
    }
    
    public boolean containsPatterUpToRoot(Node tree) {   	
    	//TODO consider what you can do to avoid confusion regarding
        // order of input tree. User may not know what that means!
        while (!this.isRoot()) {
        	
            if (this.getLiteral().equals(tree.getLiteral())) {
                if (tree.getSuccessors().isEmpty())
                	return true; // Pattern is there!
                else {
                    //Node next = tree.getSuccessor(0);
                	Node next = tree.getSuccessors().getFirst();
                    return this.getParent().containsPatterUpToRoot(next);
                }
            } else {
                /* TODO If negation found then we can skip searching !!!
                if (this.getLiteral().equals(tree.getLiteral(negation))) {
                    return false;
                }
                 */
                return this.getParent().containsPatterUpToRoot(tree);
            }
        }
        return false;
    }
    
    public boolean isRoot() {
        return this.getParent() == null;
    }
    
    public void setParent(Node n) {
        this.parent = n;
    }
    
    public LinkedList<Node> getSuccessors() {
    	return successors;
    }

    public void setSuccessors(LinkedList<Node> successors) {
        this.successors = successors;
    }

    public Node getSuccessor(int i) {
        return successors.get(i);
    }
    
    public void setSuccessor(int i, Node n) {
        successors.set(i, n);
    }
    
    public void addSuccessor(Node n) {
        successors.add(n);
    }
    
    public Node getNextLiteralNode() {
        return nextLiteralNode;
    }

    public void setNextLiteralNode(Node nextLiteralNode) {
    	// TODO Consider if it is necessary to set to null previousLiteralNode in "next" Node.
        this.nextLiteralNode = nextLiteralNode;
    }

    public Node getPreviousLiteralNode() {
        return previousLiteralNode;
    }

    public void setPreviousLiteralNode(Node previousLiteralNode) {
    	// TODO Consider if it is necessary to set to null nextLiteralNode in "previous" Node.
        this.previousLiteralNode = previousLiteralNode;
    }

    public Literal getLiteral() {
        return literal;
    }

    public void setLiteral(Literal literal) {
        this.literal = literal;
    }
    
    /**
     * @return List of all Nodes with specified Literal
     * from tree represented by this Node.
     *
     * If there are no childs with given Literal then null
     * is returned instead of empty List. We save creation
     * of one object this way.
     */
    public abstract List<Node> getNodesWithLiteral(Literal l);
    
    public abstract List<Node> getLastChilds();
        
    public String toString() {
    	if (formatter == null) throw new RuntimeException("Formatter must be set first");
    	return formatter.format(this);
    }
    
    public void setFormatter(Formatter<Node> f) {
    	formatter = f;
    }
    
    public Formatter<Node> getFormatter() {
    	return formatter;
    }
}
